#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{ \
    p += (strlen(p) - 1);\
    while(*p != '/') p--;\
}while(0)

#define SkipSpace(usercommand, pos) do{ while(1){ if(isspace(usercommand[pos]))pos++; else break; } }while(0)
// "ls -a -l -n > myfile.txt"
#define None_Redir 0
#define IN_Redir   1
#define OUT_Redir  2
#define App_Redir  3

int redir_type = None_Redir;
char* filename = NULL;

//为了方便才定义了以下变量
char cwd[SIZE * 2];
char* gArgv[NUM];
int lastcode = 0;

void Die()
{
  exit(1);
}

const char* GetHome()
{
  const char* home = getenv("HOME");
  if(home == NULL) return "/";
  return home;
}

const char* GetUserName()
{
  const char* name = getenv("USER");
  if(name == NULL)
  {
    return "None";
  }
  return name;
}

const char* GetHostName()
{
  const char* hostname = getenv("HOSTNAME");
  if(hostname == NULL)
  {
    return "None";
  }
  return hostname;
}

const char* GetCwd()
{
  const char* cwd = getenv("PWD");
  if(cwd == NULL)
  {
    return "None";
  }
  return cwd;
}

void MakeCommandLineAndPrint()
{
  char line[SIZE];
  const char* username = GetUserName();
  const char* hostname = GetHostName();
  const char* cwd = GetCwd();

  SkipPath(cwd);
  snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, strlen(cwd) == 1 ? "/" : cwd + 1);
  printf("%s", line);
  fflush(stdout);
}

int GetUserCommandAndPrint(char ret[])
{
  char usercommand[SIZE];
  char* s = fgets(usercommand, sizeof(usercommand), stdin);
  if(s == NULL)
  {
    return -1;
  }
  usercommand[strlen(usercommand) - 1] = ZERO;
  strcpy(ret, usercommand);
  // printf("echo: %s\n", usercommand);
  return strlen(usercommand);
}

void SplitCommand(char command[], size_t n)
{
  // "ls -a -l" -> "ls" "-a" "-l"
  gArgv[0] = strtok(command, SEP);
  int index = 1;
  while((gArgv[index++] = strtok(NULL, SEP)));
}

void ExecuteCommand()
{
  pid_t id = fork();
  if(id < 0)
  {
    Die();
  }
  else if(id == 0)
  {
    if(filename != NULL)
    {
      if(redir_type == IN_Redir)
      {
        int fd = open(filename, O_RDONLY);
        dup2(fd, 0);
      }
      else if(redir_type == OUT_Redir)
      {
        int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        dup2(fd, 1);
      }
      else if(redir_type == App_Redir) 
      {
        int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
        dup2(fd, 1);
      }
      else 
      {}
    }
    execvp(gArgv[0], gArgv);
    exit(errno);
  }
  else 
  {
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
      lastcode = WEXITSTATUS(status);
      if(lastcode)
      {
        printf("%s: %s: %d\n", gArgv[0], strerror(lastcode), lastcode);
      }
    }
  }
}

void Cd()
{
  const char* path = gArgv[1];
  if(path == NULL)
  {
    path = GetHome();
  }
  // 
  chdir(path);

  //刷新环境变量
  //由于直接使用snprintf的时候如果cd后面跟的有其他字符串，是什么字符串就会将这个字符串放到cwd中，
  //使得当前的环境变量变成该字符串
  char tmp[SIZE * 2];
  getcwd(tmp, sizeof(tmp));
  snprintf(cwd, sizeof(cwd), "PWD=%s", tmp);
  putenv(cwd);
}

int CheckBuildin()
{
  int flag = 0;
  const char* enterCMD = gArgv[0];
  if(strcmp(enterCMD, "cd") == 0)
  {
    flag = 1;
    Cd();
  }
  else if(strcmp(enterCMD, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
  {
    flag = 1;
    printf("%d\n", lastcode);
    lastcode = 0;
  }
  return flag;
}

void CheckRedir(char usercommand[])
{
  // "ls -a -l -n > myfile.txt"
  int pos = 0;
  int end = strlen(usercommand);
  while(pos < end)
  {
    if(usercommand[pos] == '>')
    {
      if(usercommand[pos + 1] == '>')
      {
        usercommand[pos++] = 0;
        pos++;
        redir_type = App_Redir;
        SkipSpace(usercommand, pos);
        filename = usercommand + pos;
      }
      else 
      {
        usercommand[pos++] = 0;
        redir_type = OUT_Redir;
        SkipSpace(usercommand, pos);
        filename = usercommand + pos;
      }
    }
    else if(usercommand[pos] == '<')
    {
      usercommand[pos] = 0;
      redir_type = IN_Redir;
      SkipSpace(usercommand, pos);
      filename = usercommand + pos;
    }
    else 
    {
      pos++;
    }
  }
}

int main()
{
  int quit = 0;
  while(!quit)
  {
    //0.重置
    redir_type = None_Redir;
    filename = NULL;
    // 1.输出一个自己的命令行
    MakeCommandLineAndPrint();
    
    // 2.获取用户命令字符串
    char usercommand[SIZE] = {0};
    int n = GetUserCommandAndPrint(usercommand);
    if(n <= 0)
    {
      return 1;
    }

    // 2.1 检查输入的命令是否有>> 或 > 或 <，确定是否是输入或输出重定向 
    CheckRedir(usercommand);

    // 2.2 调试函数
    //printf("usercommand: %s\n", usercommand);
    //printf("redir: %d\n", redir_type);
    //printf("filename: %s\n", filename);

    // 3.命令行字符串分割
    SplitCommand(usercommand, sizeof(usercommand));
    //int i = 0;
    // for(i = 0; gArgv[i]; i++)
    // {
    //   printf("gArgv[%d]: %s\n", i, gArgv[i]);
    // }
   
    //检测命令是否是内建命令
    n = CheckBuildin();
    if(n)
    {
      continue;
    }
    // 执行命令
    ExecuteCommand();

    //sleep(5);
  }
  return 0;
}
